namespace Game {
    using System.Collections.Generic;
    using System.IO;

    using UnityEngine;
    using UnityEditor;

#if UNITY_EDITOR && UNITY_IOS
    using UnityEditor.iOS.Xcode;
#endif

    using Newtonsoft.Json;

    public class Packer : EditorWindow {
        private enum PackMode { AllInOne, SubFolder, SubFile }

        private class BundleItem {
            public string   Name = "";
            public string   Path = "";
            public bool     Pack = false;
            public PackMode Mode = PackMode.AllInOne;
        }

        private class PackerInfo {
            public string   AppId = "com.kingsoft.xframe";
            public string   Version = "1.0.0";
            public bool     Development = true;
            public bool     EnableUpdate = true;
            public string   UpdateUrl = "";

            public List<BundleItem> Bundles = new List<BundleItem>();
        }

        [MenuItem("Tools/Packer/Editor", priority = 1)]
        public static void OpenEditor() {
            GetWindow<Packer>("Packer Editor", true, typeof(EditorWindow).Assembly.GetType("UnityEditor.InspectorWindow"));
        }

        [MenuItem("Tools/Packer/AssetBundle/Android")]
        public static void Bundle_Android() {
            Bundle_Generate(BuildTarget.Android);
        }

        [MenuItem("Tools/Packer/AssetBundle/IOS")]
        public static void Bundle_IOS() {
            Bundle_Generate(BuildTarget.iOS);
        }

        [MenuItem("Tools/Packer/AssetBundle/PC")]
        public static void Bundle_PC() {
            Bundle_Generate(BuildTarget.StandaloneWindows);
        }

        [MenuItem("Tools/Packer/AssetBundle/Clean")]
        public static void Bundle_Clean() {
            if (Directory.Exists(BundleDir)) Directory.Delete(BundleDir, true);
        }

        [MenuItem("Tools/Packer/Publish/APK")]
        public static void Publish_Android() {
            Publish_Package(BuildTarget.Android, "Publish/game.apk");
        }

        [MenuItem("Tools/Packer/Publish/XCode")]
        public static void Publish_IOS() {
            Publish_Package(BuildTarget.iOS, "Publish/XCode/");
        }

        [MenuItem("Tools/Packer/Publish/EXE")]
        public static void Publish_PC() {
            Publish_Package(BuildTarget.StandaloneWindows, "Publish/game.exe");
        }

        [MenuItem("Tools/Packer/Publish/Clean")]
        public static void Publish_Clean() {
            var files = Utils.GetFiles("Assets/StreamingAssets/", ".u", true);
            files.AddRange(new string[] {
                "Assets/StreamingAssets/Android",
                "Assets/StreamingAssets/PC",
                "Assets/StreamingAssets/IOS",
                Dir.A2BPath,
                Dir.PackagePath,
            });

            var dirs = new string[] {
                Dir.LuaAbPath,
                Dir.LuaGenPath,
            };

            foreach (var file in files) if (File.Exists(file)) File.Delete(file);
            foreach (var dir in dirs) if (Directory.Exists(dir)) Directory.Delete(dir, true);

            AssetDatabase.Refresh();
        }

        [MenuItem("Tools/Packer/CleanAll")]
        public static void CleanAll() {
            Bundle_Clean(); Publish_Clean();

            var dirs = new string[] { "Bundles", "Publish" };
            foreach (var dir in dirs) if (Directory.Exists(dir)) Directory.Delete(dir, true);
        }

        private static string TargetName(BuildTarget target) {
            switch (target) {
                case BuildTarget.Android: return "Android";
                case BuildTarget.iOS: return "IOS";
                default: return "PC";
            }
        }

        #region STEPS_ASSETBUNDLE
        private static void Bundle_ClearSetting() {
            string[] names = AssetDatabase.GetAllAssetBundleNames();
            foreach (var name in names) AssetDatabase.RemoveAssetBundleName(name, true);
            AssetDatabase.RemoveUnusedAssetBundleNames();
            AssetDatabase.Refresh();
        }
        
        private static void Bundle_CopyLua() {
            if (Directory.Exists(Dir.LuaAbPath)) Directory.Delete(Dir.LuaAbPath, true);
            Directory.CreateDirectory(Dir.LuaAbPath);

            List<string> luas = Utils.GetFiles(Dir.LuaScriptsPath, ".lua", true);
            foreach (var lua in luas) {
                string gen = lua.Replace(Dir.LuaScriptsPath, Dir.LuaAbPath) + ".txt";
                string path = Path.GetDirectoryName(gen);

                if (!Directory.Exists(path)) Directory.CreateDirectory(path);
                File.Copy(lua, gen, true);
            }

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }

        private static void Bundle_Add(string path, string name) {
            AssetImporter import = AssetImporter.GetAtPath(path);
            import.assetBundleName = name + ".u";
        }

        private static void Bundle_AddAll() {
            PackerInfo packer = JsonConvert.DeserializeObject<PackerInfo>(File.ReadAllText(PackerConfig));

            List<string> packed = new List<string>();
            foreach (BundleItem b in packer.Bundles) {
                if (b.Pack) packed.Add(b.Name);

                switch (b.Mode) {
                    case PackMode.AllInOne:
                        Bundle_Add(b.Path, b.Name);
                        break;
                    case PackMode.SubFolder:
                        DirectoryInfo dir = new DirectoryInfo(b.Path);
                        DirectoryInfo[] subs = dir.GetDirectories();
                        foreach (DirectoryInfo sub in subs) Bundle_Add(b.Path + "/" + sub.Name, b.Name + "/" + sub.Name);
                        break;
                    case PackMode.SubFile:
                        List<string> files = Utils.GetFiles(b.Path, "", true);
                        foreach (string file in files) {
                            int idx = file.IndexOf(b.Path);
                            string path = file.Substring(idx);
                            string name = b.Name + "/" + path.Substring(b.Path.Length + 1);
                            Bundle_Add(path, name);
                        }
                        break;
                    default:
                        break;
                }
            }

            AssetDatabase.Refresh();

            string[] names = AssetDatabase.GetAllAssetBundleNames();
            Dictionary<string, string> a2b = new Dictionary<string, string>();
            foreach (string name in names) {
                string[] contains = AssetDatabase.GetAssetPathsFromAssetBundle(name);
                foreach (string asset in contains) {
                    if (a2b.ContainsKey(asset)) {
                        a2b[asset] = name;
                    } else {
                        a2b.Add(asset, name);
                    }
                }
            }

            a2b.Add(Dir.A2BPath, "a2b.u");

            File.WriteAllText(Dir.A2BPath, JsonConvert.SerializeObject(a2b));

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();

            Bundle_Add(Dir.A2BPath, "a2b");
        }

        public static void Bundle_Pipiline(BuildTarget target) {
            string path = BundleDir + TargetName(target);
            if (!Directory.Exists(path)) Directory.CreateDirectory(path);
            BuildPipeline.BuildAssetBundles(path, BuildAssetBundleOptions.ChunkBasedCompression, target);
        }

        private static void Bundle_Generate(BuildTarget target, bool notice = true) {
            Bundle_ClearSetting();
            Bundle_CopyLua();
            Bundle_AddAll();
            Bundle_Pipiline(target);
            if (notice) EditorUtility.DisplayDialog("Build AssetBundles", "Success!!!", "Sure");
        }
        #endregion

        #region STEP_PUBLISH
        private static void Publish_Package(BuildTarget target, string export) {
            string outputDir = Path.GetDirectoryName(export);
            if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir);

            PackageInfo info = File.Exists(Dir.PackagePath) ? JsonConvert.DeserializeObject<PackageInfo>(File.ReadAllText(Dir.PackagePath)) : new PackageInfo();

            EditorUtility.DisplayProgressBar("PUBLISH PROGRESS", "CONFIG PROJECT", 0.1f);

            PlayerSettings.strippingLevel = StrippingLevel.StripByteCode;
            PlayerSettings.MTRendering = true;
            PlayerSettings.bundleVersion = info.BundleVersion;
            PlayerSettings.runInBackground = true;
            PlayerSettings.defaultInterfaceOrientation = UIOrientation.LandscapeLeft;
            PlayerSettings.allowedAutorotateToPortrait = false;
            PlayerSettings.allowedAutorotateToPortraitUpsideDown = false;

            if (target == BuildTarget.Android)
            {
                PlayerSettings.Android.forceSDCardPermission = true;
                PlayerSettings.Android.forceInternetPermission = true;
                PlayerSettings.Android.bundleVersionCode = info.Version;
                PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, info.AppId);
            }
            else if (target == BuildTarget.iOS)
            {
                PlayerSettings.iOS.buildNumber = info.BundleVersion;
                PlayerSettings.iOS.scriptCallOptimization = ScriptCallOptimizationLevel.SlowAndSafe;
                PlayerSettings.iOS.targetDevice = iOSTargetDevice.iPhoneAndiPad;
                PlayerSettings.SetScriptingBackend(BuildTargetGroup.iOS, ScriptingImplementation.IL2CPP);
                PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.iOS, info.AppId);
            }
            else
            {
                PlayerSettings.displayResolutionDialog = ResolutionDialogSetting.HiddenByDefault;
                PlayerSettings.resizableWindow = true;
                PlayerSettings.allowFullscreenSwitch = false;
                PlayerSettings.defaultIsFullScreen = false;
                PlayerSettings.defaultScreenWidth = 1280;
                PlayerSettings.defaultScreenHeight = 720;
            }

            if (!Directory.Exists("Assets/StreamingAssets")) Directory.CreateDirectory("Assets/StreamingAssets");

            List<string> oldBundles = Utils.GetFiles("Assets/StreamingAssets", ".u", true);
            foreach (var bundle in oldBundles) File.Delete(bundle);
            AssetDatabase.Refresh();

            EditorUtility.DisplayProgressBar("PUBLISH PROGRESS", "GENERATE LUA WRAPPERS FOR XLUA", 0.2f);
            CSObjectWrapEditor.Generator.ClearAll();
            CSObjectWrapEditor.Generator.GenAll();
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();

            EditorUtility.DisplayProgressBar("PUBLISH PROGRESS", "GENERATE ASSETBUNDLES", 0.3f);
            Bundle_Generate(target, false);

            EditorUtility.DisplayProgressBar("PUBLISH PROGRESS", "COPY ASSETBUNDLES", 0.4f);
            string dir = BundleDir + TargetName(target);

            var packer = JsonConvert.DeserializeObject<PackerInfo>(File.ReadAllText(PackerConfig));
            var a2b = new BundleItem(); a2b.Name = "a2b"; a2b.Pack = true; packer.Bundles.Add(a2b);
            SaveAsPackageInfo(packer);

            foreach (var bundle in packer.Bundles) {
                if (!bundle.Pack) continue;

                if (bundle.Mode == PackMode.AllInOne) {
                    string source = dir + "/" + bundle.Name + ".u";
                    string to = source.Replace(dir, "Assets/StreamingAssets");
                    string path = Path.GetDirectoryName(to);
                    if (!Directory.Exists(path)) Directory.CreateDirectory(path);
                    File.Copy(source, to, true);
                }
                else if (bundle.Mode == PackMode.SubFile) {
                    List<string> files = Utils.GetFiles(bundle.Path, "", true);
                    foreach (string file in files) {
                        int idx = file.IndexOf(bundle.Path);
                        string path = file.Substring(idx);
                        string name = bundle.Name + "/" + path.Substring(bundle.Path.Length + 1);
                        string source = dir + "/" + name + ".u";
                        string to = source.Replace(dir, "Assets/StreamingAssets");
                        path = Path.GetDirectoryName(to);
                        if (!Directory.Exists(path)) Directory.CreateDirectory(path);
                        File.Copy(source, to, true);
                    }
                }
                else if (bundle.Mode == PackMode.SubFolder) {
                    DirectoryInfo dirInfo = new DirectoryInfo(bundle.Path);
                    DirectoryInfo[] subs = dirInfo.GetDirectories();
                    foreach (DirectoryInfo sub in subs) {
                        string name = bundle.Name + "/" + sub.Name;
                        string source = dir + "/" + name + ".u";
                        string to = source.Replace(dir, "Assets/StreamingAssets");
                        string path = Path.GetDirectoryName(to);
                        if (!Directory.Exists(path)) Directory.CreateDirectory(path);
                        File.Copy(source, to, true);
                    }
                }
            }
            File.Copy(dir + "/" + TargetName(target), "Assets/StreamingAssets/" + TargetName(target), true);

            BuildOptions opt = BuildOptions.CompressWithLz4;
            if (info.Development) opt = BuildOptions.CompressWithLz4 | BuildOptions.Development | BuildOptions.SymlinkLibraries | BuildOptions.ConnectWithProfiler;

            List<string> scenes = new List<string>();
            foreach (var scene in EditorBuildSettings.scenes) {
                if (scene.enabled && File.Exists(scene.path))
                    scenes.Add(scene.path);
            }

            EditorUtility.DisplayProgressBar("PUBLISH PROGRESS", "START BUILD PLAYER", 0.5f);
            BuildPipeline.BuildPlayer(scenes.ToArray(), export, target, opt);

#if UNITY_EDITOR && UNITY_IOS
            if (target == BuildTarget.iOS) {
                string xcode = PBXProject.GetPBXProjectPath(export);
                PBXProject project = new PBXProject();
                project.ReadFromFile(xcode);

                string target = project.TargetGuidByName("Unity-iPhone");
                project.AddFileToBuild(target, project.AddFile("usr/lib/libz.dylib", "Frameworks/libz.dylib", PBXSourceTree.Sdk));
                project.AddFileToBuild(target, project.AddFile("usr/lib/libc++.dylib", "Frameworks/libc++.dylib", PBXSourceTree.Sdk));
                project.AddFileToBuild(target, project.AddFile("usr/lib/libstdc++.6.0.9.tbd", "Frameworks/libstdc++.6.0.9.tbd", PBXSourceTree.Sdk));
                project.AddFrameworkToProject(target, "CoreTelephony.framework", true);
                project.AddFrameworkToProject(target, "CoreAudio.framework", true);
                project.AddFrameworkToProject(target, "ReplyKit.framework", true);
                project.AddBuildProperty(target, "OTHER_LDFLAGS", "-ObjC");
                project.AddBuildProperty(target, "OTHER_LDFLAGS", "-licucore");
                project.AddBuildProperty(target, "ENABLE_BITCODE", "NO");
                project.WriteToFile(xcode);

                PlistDocument plist = new PlistDocument();
                plist.ReadFromFile(export + "Info.plist");

                PlistElementDict root = plist.root;
                PlistElementDict security = root.CreateDict("NSAppTransportSecurity");
                security.SetBoolean("NSAllowsArbitraryLoads", true);
                root.SetString("NSMicrophoneUsageDescription", "support replaykit");
                root.SetString("NSCameraUsageDescription", "support replaykit");
                root.SetString("NSBluetoothPeripheralUsageDescription", "used by BETOP joystick");
                root.SetString("UIRequiresFullScreen", "yes");

                plist.WriteToFile(export + "Info.plist");
            }
#endif

            EditorUtility.DisplayDialog("Build Player", "Success!!!", "Sure");
        }
        #endregion

        #region EDITOR_UI
        private Vector2 scroll = Vector2.zero;
        private string selected = "";
        private GUIStyle titleStyle = null; 
        private PackerInfo packer = null;

        private void OnGUI() {
            if (packer == null) {
                if (!File.Exists(PackerConfig)) {
                    packer = new PackerInfo();
                } else {
                    packer = JsonConvert.DeserializeObject<PackerInfo>(File.ReadAllText(PackerConfig));
                }
            }

            if (titleStyle == null) {
                titleStyle = new GUIStyle();
                titleStyle.fontSize = 16;
                titleStyle.normal.textColor = Color.gray;
                titleStyle.alignment = TextAnchor.MiddleCenter;
            }

            Color backup = GUI.color;
            GUILayout.Space(10);

            GUILayout.BeginHorizontal();
            {
                GUILayout.Label("Settings", titleStyle);
            }
            GUILayout.EndHorizontal();

            GUILayout.Space(5);

            GUILayout.BeginHorizontal();
            {
                GUILayout.Label("App ID", GUILayout.Width(100));
                packer.AppId = GUILayout.TextField(packer.AppId, GUILayout.ExpandWidth(true));
            }
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            {
                GUILayout.Label("Version", GUILayout.Width(100));
                string oldVersion = packer.Version;
                string newVersion = GUILayout.TextField(oldVersion, GUILayout.ExpandWidth(true));

                if (oldVersion != newVersion && !string.IsNullOrEmpty(newVersion)) {
                    int major, minor, build; GetIntVersion(newVersion, out major, out minor, out build);
                }
            }
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            {
                GUILayout.Label("Deveploment", GUILayout.Width(100));
                packer.Development = GUILayout.Toggle(packer.Development, "");
            }
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            {
                GUILayout.Label("Enable Update", GUILayout.Width(100));
                packer.EnableUpdate = GUILayout.Toggle(packer.EnableUpdate, "");
            }
            GUILayout.EndHorizontal();
            if (packer.EnableUpdate) {
                GUILayout.BeginHorizontal();
                {
                    GUILayout.Label("Update Url", GUILayout.Width(100));
                    packer.UpdateUrl = GUILayout.TextField(packer.UpdateUrl, GUILayout.ExpandWidth(true));
                }
                GUILayout.EndHorizontal();
            }

            GUILayout.Space(10);

            GUILayout.BeginHorizontal();
            {
                GUILayout.Label("AssetBundles", titleStyle);
            }
            GUILayout.EndHorizontal();
            scroll = GUILayout.BeginScrollView(scroll, GUILayout.ExpandHeight(true));
            {
                GUILayout.BeginVertical("GroupBox", GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
                {
                    foreach (var b in packer.Bundles) {
                        GUILayout.BeginVertical(b.Name == selected ? "SelectionRect" : "box");
                        {
                            GUILayout.BeginHorizontal();
                            {
                                GUILayout.Label("Name", GUILayout.Width(80));
                                b.Name = GUILayout.TextArea(b.Name);
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            {
                                GUILayout.Label("Path", GUILayout.Width(80));
                                b.Path = GUILayout.TextArea(b.Path);

                                if (GUILayout.Button("", "AssetLabel Icon")) {
                                    if (Selection.activeObject == null) {
                                        EditorUtility.DisplayDialog("出错了", "请在Project视图中选中需要打包的文件或文件夹", "OK");
                                    } else {
                                        b.Path = AssetDatabase.GetAssetPath(Selection.activeObject);
                                    }
                                }
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            {
                                GUILayout.Label("Pack Mode", GUILayout.Width(80));
                                GUILayout.FlexibleSpace();
                                b.Mode = (PackMode)EditorGUILayout.EnumPopup(b.Mode);
                                b.Pack = GUILayout.Toggle(b.Pack, "");
                            }
                            GUILayout.EndHorizontal();
                        }
                        GUILayout.EndVertical();

                        bool isSelect = Event.current.type == EventType.MouseUp && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition);
                        if (isSelect) {
                            selected = selected == b.Name ? string.Empty : b.Name;
                            Repaint();
                        }
                    }
                }
                GUILayout.EndVertical();
            }
            GUILayout.EndScrollView();

            GUI.color = Color.green;
            GUILayout.BeginHorizontal();
            {
                if (GUILayout.Button("Create")) {
                    packer.Bundles.Add(new BundleItem());
                    Repaint();
                }

                GUI.color = Color.red;
                if (GUILayout.Button("Delete")) {
                    if (!string.IsNullOrEmpty(selected)) {
                        for (int i = 0; i < packer.Bundles.Count; ++i) {
                            if (packer.Bundles[i].Name == selected) {
                                packer.Bundles.RemoveAt(i);
                                break;
                            }
                        }

                        selected = "";
                        Repaint();
                    } else {
                        EditorUtility.DisplayDialog("ERROR", "You should select an item to delete!", "Got it");
                    }
                }
                GUI.color = Color.green;
            }
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            {
                if (GUILayout.Button("Save All")) {
                    List<string> names = new List<string>();
                    foreach (BundleItem info in packer.Bundles) {
                        if (names.Contains(info.Name)) {
                            EditorUtility.DisplayDialog("ERROR", "Duplicated bundle name : " + info.Name, "Fix it");
                        } else {
                            names.Add(info.Name);
                        }
                    }

                    File.WriteAllText(PackerConfig, JsonConvert.SerializeObject(packer));

                    SaveAsPackageInfo(packer);
                }
            }
            GUILayout.EndHorizontal();

            GUI.color = backup;
        }

        private static void GetIntVersion(string sVersion, out int major, out int minor, out int build) {
            major = 1; minor = 0; build = 0;
            string[] subs = sVersion.Trim().Split('.');
            if (subs.Length != 3){
                throw new System.Exception("Version Code Format must be version=xxx.yyy.zzz, such as \'1.0.0\'.");
            }
            major = int.Parse(subs[0]);
            minor = int.Parse(subs[1]);
            build = int.Parse(subs[2]);
        }

        private static void SaveAsPackageInfo(PackerInfo packer) {
            string path, name;
            
            PackageInfo package     = new PackageInfo();
            package.AppId           = packer.AppId;
            package.Development     = packer.Development;
            package.EnableUpdate    = packer.EnableUpdate;
            package.UpdateUrl       = packer.UpdateUrl;
            GetIntVersion(packer.Version, out package.Major, out package.Minor, out package.Build);
            foreach (var b in packer.Bundles) {
                if (!b.Pack) continue;

                if (b.Mode == PackMode.AllInOne) {
                    package.PackedBundles.Add(b.Name + ".u");
                }
                else if (b.Mode == PackMode.SubFile) {
                    List<string> files = Utils.GetFiles(b.Path, "", true);
                    foreach (string file in files) {
                        int idx = file.IndexOf(b.Path);
                        path = file.Substring(idx);
                        name = b.Name + "/" + path.Substring(b.Path.Length + 1);
                        package.PackedBundles.Add(name + ".u");
                    }
                }
                else if (b.Mode == PackMode.SubFolder) {
                    DirectoryInfo dirInfo = new DirectoryInfo(b.Path);
                    DirectoryInfo[] subs = dirInfo.GetDirectories();
                    foreach (DirectoryInfo sub in subs) {
                        name = b.Name + "/" + sub.Name;
                        package.PackedBundles.Add(name + ".u");
                    }
                }
            }

            path = "Assets/StreamingAssets";
            if (!Directory.Exists(path)) {
                Directory.CreateDirectory(path);
            }

            File.WriteAllText(Dir.PackagePath, JsonConvert.SerializeObject(package));
        }

        #endregion

        private static readonly string BundleDir        = "Bundles/";
        private static readonly string PackerConfig     = "Assets/Game/Packer.json";
    }
}
